/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.solr.internal.csv;

/**
 * A simple StringBuffer replacement that aims to reduce copying as much as possible. The buffer
 * grows as necessary. This class is not thread safe.
 */
public class CharBuffer {

  private char[] c;

  /**
   * Actually used number of characters in the array. It is also the index at which a new character
   * will be inserted into <code>c</code>.
   */
  private int length;

  /** Creates a new CharBuffer with an initial capacity of 32 characters. */
  public CharBuffer() {
    this(32);
  }

  /** Creates a new CharBuffer with an initial capacity of <code>length</code> characters. */
  public CharBuffer(final int length) {
    if (length == 0) {
      throw new IllegalArgumentException("Can't create an empty CharBuffer");
    }
    this.c = new char[length];
  }

  /** Empties the buffer. The capacity still remains the same, so no memory is freed. */
  public void clear() {
    length = 0;
  }

  /**
   * Returns the number of characters in the buffer.
   *
   * @return the number of characters
   */
  public int length() {
    return length;
  }

  /**
   * Returns the current capacity of the buffer.
   *
   * @return the maximum number of characters that can be stored in this buffer without resizing it.
   */
  public int capacity() {
    return c.length;
  }

  /**
   * Appends the contents of <code>cb</code> to the end of this CharBuffer.
   *
   * @param cb the CharBuffer to append or null
   */
  public void append(final CharBuffer cb) {
    if (cb == null) {
      return;
    }
    provideCapacity(length + cb.length);
    System.arraycopy(cb.c, 0, c, length, cb.length);
    length += cb.length;
  }

  /**
   * Appends <code>s</code> to the end of this CharBuffer. This method involves copying the new data
   * once!
   *
   * @param s the String to append or null
   */
  public void append(final String s) {
    if (s == null) {
      return;
    }
    append(s.toCharArray());
  }

  /**
   * Appends <code>sb</code> to the end of this CharBuffer. This method involves copying the new
   * data once!
   *
   * @param sb the StringBuffer to append or null
   */
  public void append(final StringBuilder sb) {
    if (sb == null) {
      return;
    }
    provideCapacity(length + sb.length());
    sb.getChars(0, sb.length(), c, length);
    length += sb.length();
  }

  /**
   * Appends <code>data</code> to the end of this CharBuffer. This method involves copying the new
   * data once!
   *
   * @param data the char[] to append or null
   */
  public void append(final char[] data) {
    if (data == null) {
      return;
    }
    provideCapacity(length + data.length);
    System.arraycopy(data, 0, c, length, data.length);
    length += data.length;
  }

  /**
   * Appends a single character to the end of this CharBuffer. This method involves copying the new
   * data once!
   *
   * @param data the char to append
   */
  public void append(final char data) {
    provideCapacity(length + 1);
    c[length] = data;
    length++;
  }

  /**
   * Shrinks the capacity of the buffer to the current length if necessary. This method involves
   * copying the data once!
   */
  public void shrink() {
    if (c.length == length) {
      return;
    }
    char[] newc = new char[length];
    System.arraycopy(c, 0, newc, 0, length);
    c = newc;
  }

  /** Removes trailing whitespace. */
  public void trimTrailingWhitespace() {
    while (length > 0 && Character.isWhitespace(c[length - 1])) {
      length--;
    }
  }

  /**
   * Returns the contents of the buffer as a char[]. The returned array may be the internal array of
   * the buffer, so the caller must take care when modifying it. This method avoids copying if the
   * caller knows the exact capacity before.
   */
  public char[] getCharacters() {
    if (c.length == length) {
      return c;
    }
    char[] chars = new char[length];
    System.arraycopy(c, 0, chars, 0, length);
    return chars;
  }

  /** Returns the character at the specified position. */
  public char charAt(int pos) {
    return c[pos];
  }

  /**
   * Converts the contents of the buffer into a StringBuffer. This method involves copying the new
   * data once!
   */
  @Override
  public String toString() {
    return new String(c, 0, length);
  }

  /** Copies the data into a new array of at least <code>capacity</code> size. */
  public void provideCapacity(final int capacity) {
    if (c.length >= capacity) {
      return;
    }
    int newcapacity = ((capacity * 3) >> 1) + 1;
    char[] newc = new char[newcapacity];
    System.arraycopy(c, 0, newc, 0, length);
    c = newc;
  }
}
